﻿using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
using Poly2Tri;

namespace DoomWad
{
	public struct MapTriangle
	{
		public int v1;
		public int v2;
		public int v3;

		public int ld1;
		public int ld2;
		public int ld3;

		public bool leftside1;
		public bool leftside2;
		public bool leftside3;

		public int sector;

		public bool hasLinedef { get { return ld1 != -1 || ld2 != -1 || ld3 != -1; } }
	}

	public class WadMapImporter
	{
		public class MeshObject
		{
			public bool isWall;				// flat or wall/side
			public Transform transform;		// transform link
			public Mesh mesh;				// direct link to mesh
			public Material mat;			// direct link to material
			public Vector3 uva;				// u, v, area (u*v) of uv bounds (uv aabb)
		}

		public const float SHORT2FLOAT = 1f / ( float ) WadDefs.PIXELS_PER_UNIT;

		public WadReader reader;
		public WadMapData map;

		// map import params
		public float scale = 1f;
		public FilterMode textureFilter = FilterMode.Point;
		public bool makeColliders = true;
		public bool makeSkyMask = false;
		public bool makeTextureAssets = false;

		// lightmapping params
		public bool generateLightmapUV = true;
		public bool generateIllumMaterials = false;
		public bool allAreIllum = false;
		public float illumEmission = 7.5f;

		public ConstrainedPointSet pointSet;
		public List<MapTriangle> triangles;

		private Transform rootTransform;
		private List<MeshObject> meshObjects;
		
		// Final runtime map
		private MapFlat[] flats; // flatIndex = sectorIndex * FlatType.MAX + FlatType
		private MapWall[] walls; // wallIndex = linedefIndex

		// Текстура будет отмечена "излучаемой" при любом из вхождений данных частей в имя текстуры.
		private static string[] illumTexNameParts = {
			/* FLATS */ "water", "lava", "nukage", "tlite6", "ceil1_2", "ceil1_3", "flat2", "flat14", "flat17", "floor1_7", "lite", "blood", "gate",
			/* WALLS */ "lite", "exitsign", "doorred", "doorblu", "dooryel" };


		public bool Import( string wadfile, string mapname )
		{
			reader = new WadReader( wadfile );
			reader.makeTextureAssets = makeTextureAssets;
			
			if ( !reader.isWad )
				throw new UnityException( "Файл " + wadfile + " имет отличный id от IWAD или PWAD" );

			map = reader.LoadMap( mapname, textureFilter );
			if ( map == null )
				return false;

			// Создаем корневой объект
			rootTransform = new GameObject( mapname ).transform;
			rootTransform.localScale = Vector3.one * scale;

			// Триангулирем
			Triangulate();
			FinishTriangles();

			// Меши
			meshObjects = new List<MeshObject>();

			// карта
			flats = new MapFlat[ map.sectors.Length * ( int ) FlatType.MAX ];
			walls = new MapWall[ map.linedefs.Length ];

			// Когда готовы треугольники, то просто запиливаем сектора (пол и потолки без стен)
			for ( int i = 0; i < map.sectors.Length; i++ )
				BuildSector( i );

			// Walls
			for ( int i = 0; i < map.linedefs.Length; i++ )
				BuildWall( i );

			// Генерируем лайтмапы
			GenerateLightmapUVs();

			return true;
		}

		private void Triangulate()
		{
			// BOUNDS
			Rect bbox = map.GetBoundingBox();
			bbox = MathUtils.GetTaperRect( bbox, -500f );
			List<TriangulationPoint> bounds = new List<TriangulationPoint>();
			bounds.Add( new TriangulationPoint( bbox.xMin, bbox.yMin ) );
			bounds.Add( new TriangulationPoint( bbox.xMax, bbox.yMin ) );
			bounds.Add( new TriangulationPoint( bbox.xMin, bbox.yMax ) );
			bounds.Add( new TriangulationPoint( bbox.xMax, bbox.yMax ) );

			// POINTS
			List<TriangulationPoint> points = new List<TriangulationPoint>();
			for ( int i = 0; i < map.onlyLinedefVertexCount; i++ )
			{
				mapvertex_t v = map.vertices[ i ];
				TriangulationPoint p = new TriangulationPoint( v.x, v.y );
				points.Add( p );
			}
			points.AddRange( bounds );

			// CONSTRAINTS
			List<TriangulationConstraint> constraints = new List<TriangulationConstraint>();
			for ( int i = 0; i < map.linedefs.Length; i++ )
			{
				maplinedef_t ld = map.linedefs[ i ];
				TriangulationConstraint c = new TriangulationConstraint( points[ ld.v1 ], points[ ld.v2 ] );
				constraints.Add( c );
			}

			// POINTSET
			pointSet = new ConstrainedPointSet( points, constraints );

			// TRIANGULATION
			DTSweepContext tcx = new DTSweepContext();
			tcx.PrepareTriangulation( pointSet );
			DTSweep.Triangulate( tcx );
		}

		private void FinishTriangles()
		{
			triangles = new List<MapTriangle>();

			for ( int i = 0; i < pointSet.Triangles.Count; i++ )
			{
				DelaunayTriangle dt = pointSet.Triangles[ i ];
				MapTriangle t = new MapTriangle();

				t.v1 = map.FindClosestVertex( new Vector2( ( float ) dt.Points[ 0 ].X, ( float ) dt.Points[ 0 ].Y ), 0.1f );
				if ( t.v1 == -1 )
					continue;

				t.v2 = map.FindClosestVertex( new Vector2( ( float ) dt.Points[ 1 ].X, ( float ) dt.Points[ 1 ].Y ), 0.1f );
				if ( t.v2 == -1 )
					continue;

				t.v3 = map.FindClosestVertex( new Vector2( ( float ) dt.Points[ 2 ].X, ( float ) dt.Points[ 2 ].Y ), 0.1f );
				if ( t.v3 == -1 )
					continue;

				t.ld1 = map.FindLindefByVertices( t.v1, t.v2, ref t.leftside1 );
				t.ld2 = map.FindLindefByVertices( t.v2, t.v3, ref t.leftside2 );
				t.ld3 = map.FindLindefByVertices( t.v3, t.v1, ref t.leftside3 );

				t.sector = GetSectorByTriangle( t );

				triangles.Add( t );
			}

			// Особый случай: находим сектора для треугольников по соседям

			for ( int i = 0; i < triangles.Count; i++ )
			{
				MapTriangle t = triangles[ i ];
				if ( t.sector == -1 )
				{
					t.sector = FindSectorByTriangle( i );
					triangles[ i ] = t;
				}
			}
		}

		public int GetSectorByTriangle( MapTriangle t )
		{
			int sIndex;
			
			sIndex = map.GetSectorByLinedef( t.ld3, t.leftside3, true );
			if ( sIndex != -1 )
				return sIndex;

			sIndex = map.GetSectorByLinedef( t.ld2, t.leftside2, true );
			if ( sIndex != -1 )
				return sIndex;

			sIndex = map.GetSectorByLinedef( t.ld1, t.leftside1, true );
			if ( sIndex != -1 )
				return sIndex;

			return -1;
		}

		public int FindSectorByTriangle( int tIndex )
		{
			List<int> availableList = new List<int>();
			List<int> checkList = new List<int>();

			for ( int i = 0; i < triangles.Count; i++ )
				if ( i != tIndex )
					availableList.Add( i );
			checkList.Add( tIndex );

			int maxIterations = 32;
			int iteration = 0;

			Log.WriteLine( "[triangle: " + tIndex + "]" );
			while ( availableList.Count > 0 )
			{
				Log.WriteLine( "  iteration: " + iteration );
				Log.WriteLine( "    available count: " + availableList.Count );
				Log.WriteLine( "    check count: " + checkList.Count );
				Log.WriteLine( "    check list: " + checkList.JoinToString() );

				// Смотрим, есть ли у нас лайндеф

				foreach ( int i in checkList )
				{
					MapTriangle t = triangles[ i ];
					if ( t.hasLinedef )
						return GetSectorByTriangle( t );
				}

				// Если нет, то расширяем соседей

				List<int> neighbourList = new List<int>();
				foreach ( int i in checkList )
				{
					MapTriangle t = triangles[ i ];
					for ( int j = 0; j < 3; j++ )
					{
						int vIndex1 = j == 0 ? t.v1 : j == 1 ? t.v2 : t.v3;
						int vIndex2 = j == 0 ? t.v2 : j == 1 ? t.v3 : t.v1;

						int n = FindTriangleByVertices( availableList, vIndex1, vIndex2 );
						if ( n != -1 )
						{
							neighbourList.Add( n );
							availableList.Remove( n );
						}
					}
				}

				checkList = neighbourList;
				
				iteration++;
				if ( iteration >= maxIterations )
				{
					Debug.LogError( "Слишком много итераций" );
					break;
				}
			}

			return -1;
		}

		public int FindTriangleByVertices( List<int> triangleIndices, int v1, int v2 )
		{
			foreach ( int i in triangleIndices )
			{
				MapTriangle t = triangles[ i ];

				// CW winding
				if ( ( t.v1 == v1 && t.v2 == v2 ) ||
					( t.v2 == v1 && t.v3 == v2 ) ||
					( t.v3 == v1 && t.v1 == v2 ) )
				{
					return i;
				}

				// CCW winding
				if ( ( t.v1 == v2 && t.v2 == v1 ) ||
					( t.v2 == v2 && t.v3 == v1 ) ||
					( t.v3 == v2 && t.v1 == v1 ) )
				{
					return i;
				}
			}

			return -1;
		}

		private void BuildSector( int sIndex )
		{
			if ( map.IsSectorDegenerative( sIndex ) )
				return;

			mapsector_t s = map.sectors[ sIndex ];
			
			// - Отбираем треугольники сектора
			// - Формируем массив уникальных индексов вершин, которые испоьлзуются треугольниками (для маппинга с глобального индекса на локальный)
			
			List<MapTriangle> sectorTriangles = new List<MapTriangle>();
			List<int> vertexIndices = new List<int>();
			
			foreach ( MapTriangle t in triangles )
			{
				if ( t.sector == sIndex )
				{
					sectorTriangles.Add( t );
					vertexIndices.AddUnique( t.v1 );
					vertexIndices.AddUnique( t.v2 );
					vertexIndices.AddUnique( t.v3 );
				}
			}

			// Запиливаем массив вершин сектора
			Vector2[] vertices = new Vector2[ vertexIndices.Count];
			for ( int i = 0; i < vertices.Length; i++ )
			{
				vertices[ i ] = SHORT2FLOAT * ( Vector2 ) map.vertices[ vertexIndices[ i ] ];
			}

			// Мапим индексы треугольников с глобального массива вершин на локальный
			for ( int i = 0; i < sectorTriangles.Count; i++ )
			{
				MapTriangle t = sectorTriangles[ i ];
				t.v1 = vertexIndices.IndexOf( t.v1 );
				t.v2 = vertexIndices.IndexOf( t.v2 );
				t.v3 = vertexIndices.IndexOf( t.v3 );
				sectorTriangles[ i ] = t;
			}

			// Создаем два флата - пол и потолок
			MapTriangle[] triArray = sectorTriangles.ToArray();
			int[] vertIndArray = vertexIndices.ToArray();
			BuildSectorFlat( sIndex, vertIndArray, vertices, triArray, false );
			BuildSectorFlat( sIndex, vertIndArray, vertices, triArray, true );
		}

		private void BuildSectorFlat( int sIndex, int[] vertexIndices, Vector2[] sectorVertices, MapTriangle[] sectorTriangles, bool isCeiling )
		{
			mapsector_t s = map.sectors[ sIndex ];

			// Проверяем не является ли текстура маской неба
			string texname = isCeiling ? s.ceilingpic : s.floorpic;
			bool isSky = texname == WadReader.skyTexName;

			// Освещение
			float brightness = ( float ) s.lightlevel / ( float ) short.MaxValue;
			Color color = new Color( brightness, brightness, brightness, 1f );

			// UV Tiling
			float uvTiling = ( float ) WadDefs.PIXELS_PER_UNIT / ( float ) WadDefs.PIXELS_PER_FLAT;

			// Создаем материал с нужной текстурой
			Material mat = CreateMaterial( texname, false );

			// Высоты пола/потолка
			float height = SHORT2FLOAT * ( isCeiling ? /*isSky ? map.skyCeilingHeight :*/ s.ceilingheight : s.floorheight );

			//-------------------------------------------------------------------------------------
			// MESH
			//-------------------------------------------------------------------------------------

			Mesh mesh = new Mesh();
			mesh.name = "sector_" + sIndex + "_" + ( isCeiling ? "ceiling" : "floor" );

			Vector3[] vertices = new Vector3[ sectorVertices.Length ];
			Color[] colors = new Color[ sectorVertices.Length ];
			int[] triangles = new int[ sectorTriangles.Length * 3 ];
			Vector2[] uv = new Vector2[ sectorVertices.Length ];

			// Находим центр
			Vector3 center = Vector3.zero;
			for ( int i = 0; i < sectorVertices.Length; i++ )
			{
				Vector2 p = sectorVertices[ i ];
				center += new Vector3( p.x, 0f, p.y );
			}
			center /= ( float ) sectorVertices.Length;
			center.y = height;

			if ( float.IsNaN( center.x ) || float.IsNaN( center.z ) )
			{
				Log.WriteLine( "[Flat: " + mesh.name + "]" );
				Log.WriteLine( "  vertex count: " + sectorVertices.Length );
				Log.WriteLine( "  vertices: " + sectorVertices.JoinToString() );
			}

			for ( int i = 0; i < vertices.Length; i++ )
			{
				Vector2 p = sectorVertices[ i ];
				vertices[ i ] = new Vector3( p.x, height, p.y ) - center;
				colors[ i ] = color;
				uv[ i ] = p * uvTiling;
			}

			for ( int i = 0; i < sectorTriangles.Length; i++ )
			{
				MapTriangle t = sectorTriangles[ i ];
				if ( isCeiling )
				{
					triangles[ i * 3 + 0 ] = t.v1;
					triangles[ i * 3 + 1 ] = t.v2;
					triangles[ i * 3 + 2 ] = t.v3;
				}
				else
				{
					triangles[ i * 3 + 0 ] = t.v1;
					triangles[ i * 3 + 1 ] = t.v3;
					triangles[ i * 3 + 2 ] = t.v2;
				}
			}

			mesh.vertices = vertices;
			mesh.colors = colors;
			mesh.triangles = triangles;
			mesh.uv = uv;

			mesh.RecalculateNormals();
			mesh.RecalculateBounds();

			//-------------------------------------------------------------------------------------
			// Создаем объект и вешаем на него меш c материалом
			//-------------------------------------------------------------------------------------

			GameObject go = CreateGameObject( rootTransform, center, mesh, mat, false, isSky );
			MapFlat flat = go.AddComponent<MapFlat>();
			flat.mesh = mesh;
			flat.mat = mat;
			flat.vertexIndices = vertexIndices;
			int flatIndex = GetFlatIndex( sIndex, isCeiling );
			flats[ flatIndex ] = flat;
		}

		private int GetFlatIndex( int sectorIndex, bool isCeiling )
		{
			return sectorIndex * ( int ) FlatType.MAX + ( int ) ( isCeiling ? FlatType.CEILING : FlatType.FLOOR );
		}

		private MapFlat GetFlat( int sectorIndex, bool isCeiling )
		{
			int flatIndex = GetFlatIndex( sectorIndex, isCeiling );
			return flats[ flatIndex ];
		}

		private void BuildWall( int linedefIndex )
		{
			maplinedef_t ld = map.linedefs[ linedefIndex ];

			bool twoSided = ( ( LineDefFlags ) ld.flags & LineDefFlags.ML_TWOSIDED ) == LineDefFlags.ML_TWOSIDED;
			bool topUnpegged = ( ( LineDefFlags ) ld.flags & LineDefFlags.ML_DONTPEGTOP ) == LineDefFlags.ML_DONTPEGTOP;
			bool bottomUnpegged = ( ( LineDefFlags ) ld.flags & LineDefFlags.ML_DONTPEGBOTTOM ) == LineDefFlags.ML_DONTPEGBOTTOM;

			int v1 = ld.v1;
			int v2 = ld.v2;

			GameObject go = new GameObject( "linedef_" + linedefIndex );
			go.transform.parent = rootTransform;
			go.transform.localScale = Vector3.one;
			go.transform.localPosition = Vector3.zero;

			MapWall wall = go.AddComponent<MapWall>();
			wall.flags = ( LineDefFlags ) ld.flags;
			walls[ linedefIndex ] = wall;

			// TWO SIDED
			if ( twoSided )
			{
				// Всего потенциально может получиться 6 квадов по 3 на каждую сторону

				mapsidedef_t rsd = map.sidedefs[ ld.rsidenum ];
				mapsidedef_t lsd = map.sidedefs[ ld.lsidenum ];
				
				mapsector_t rs = map.sectors[ rsd.sector ];
				mapsector_t ls = map.sectors[ lsd.sector ];

				float rfloorHeight = SHORT2FLOAT * rs.floorheight;
				float rceilingHeight = SHORT2FLOAT * rs.ceilingheight;

				float lfloorHeight = SHORT2FLOAT * ls.floorheight;
				float lceilingHeight = SHORT2FLOAT * ls.ceilingheight;

				MapFlat upperFloor = rfloorHeight < lfloorHeight ? GetFlat( lsd.sector, false ) : GetFlat( rsd.sector, false );
				MapFlat lowerCeiling = lceilingHeight < rceilingHeight ? GetFlat( lsd.sector, true ) : GetFlat( rsd.sector, true );

				// VALIDATE SIDEDEF TEXTURES

				if ( rfloorHeight != lfloorHeight )
				{
					if ( rsd.bottomtexture == "-" )
						rsd.bottomtexture = lsd.bottomtexture;
					else if ( lsd.bottomtexture == "-" )
						lsd.bottomtexture = rsd.bottomtexture;
				}

				if ( rceilingHeight != lceilingHeight )
				{
					if ( rsd.toptexture == "-" )
						rsd.toptexture = lsd.toptexture;
					else if ( lsd.toptexture == "-" )
						lsd.toptexture = rsd.toptexture;
				}

				map.sidedefs[ ld.rsidenum ] = rsd;
				map.sidedefs[ ld.lsidenum ] = lsd;

				// TOP WALL

				if ( rceilingHeight > lceilingHeight )
				{
					BuildWallSide( ld.rsidenum, v1, v2, lceilingHeight, rceilingHeight, SideType.TOP, twoSided, !topUnpegged, wall, false, 
						GetFlat( rsd.sector, true ), 
						GetFlat( lsd.sector, true ) );
				}
				else if ( lceilingHeight > rceilingHeight )
				{
					BuildWallSide( ld.lsidenum, v2, v1, rceilingHeight, lceilingHeight, SideType.TOP, twoSided, !topUnpegged, wall, true,
						GetFlat( lsd.sector, true ),
						GetFlat( rsd.sector, true ) );
				}

				// MIDDLE WALL

				if ( rsd.midtexture != "-" )
				{
					BuildWallSide( ld.rsidenum, v1, v2, upperFloor.height, lowerCeiling.height, SideType.MIDDLE, twoSided, bottomUnpegged, wall, false,
						lowerCeiling,
						upperFloor );
				}

				if ( lsd.midtexture != "-" )
				{
					BuildWallSide( ld.lsidenum, v2, v1, upperFloor.height, lowerCeiling.height, SideType.MIDDLE, twoSided, bottomUnpegged, wall, true,
						lowerCeiling,
						upperFloor );
				}

				// BOTTOM WALL

				if ( rfloorHeight < lfloorHeight )
				{
					BuildWallSide( ld.rsidenum, v1, v2, rfloorHeight, lfloorHeight, SideType.BOTTOM, twoSided, bottomUnpegged, wall, false,
						GetFlat( lsd.sector, false ),
						GetFlat( rsd.sector, false ) );
				}
				else if ( lfloorHeight < rfloorHeight )
				{
					BuildWallSide( ld.lsidenum, v2, v1, lfloorHeight, rfloorHeight, SideType.BOTTOM, twoSided, bottomUnpegged, wall, true,
						GetFlat( rsd.sector, false ),
						GetFlat( lsd.sector, false ) );
				}
			}
			// ONE SIDED
			else
			{
				bool isLeftSide = false;
				int sdIndex = ld.rsidenum;
				if ( sdIndex == -1 )
				{
					isLeftSide = true;
					sdIndex = ld.lsidenum;
					MathUtils.Swap( ref v1, ref v2 );
				}

				mapsidedef_t sd = map.sidedefs[ sdIndex ];
				mapsector_t s = map.sectors[ sd.sector ];

				// Высоты пола/потолка
				float floorHeight = SHORT2FLOAT * s.floorheight;
				float ceilingHeight = SHORT2FLOAT * s.ceilingheight;

				BuildWallSide( ld.rsidenum, v1, v2, floorHeight, ceilingHeight, SideType.MIDDLE, twoSided, bottomUnpegged, wall, isLeftSide,
						GetFlat( sd.sector, true ),
						GetFlat( sd.sector, false ) );
			}
		}

		private void BuildWallSide( int sdIndex, int vIndex1, int vIndex2, float floorHeight, float ceilingHeight, SideType type, bool twoSided, bool pegToBottom, 
			MapWall wall, bool isLeftSide,
			MapFlat upperFlat, MapFlat lowerFlat )
		{
			if ( sdIndex == -1 )
				return;

			Vector2 v1 = SHORT2FLOAT * ( Vector2 ) map.vertices[ vIndex1 ];
			Vector2 v2 = SHORT2FLOAT * ( Vector2 ) map.vertices[ vIndex2 ];

			mapsidedef_t sd = map.sidedefs[ sdIndex ];

			// Находим материал с нужной текстурой
			string texname = type == SideType.TOP ? sd.toptexture : type == SideType.BOTTOM ? sd.bottomtexture : sd.midtexture;
			if ( texname == "-" )
				return;
			Material mat = CreateMaterial( texname, twoSided && type == SideType.MIDDLE );
			
			// Центр стенки
			Vector3 center = ( new Vector3( v1.x, floorHeight, v1.y ) + new Vector3( v2.x, ceilingHeight, v2.y ) ) * 0.5f;

			//-------------------------------------------------------------------------------------
			// MESH
			//-------------------------------------------------------------------------------------

			Mesh mesh = new Mesh();
			mesh.name = "sidedef_" + sdIndex + "_" + type.ToString();

			Vector3[] vertices = new Vector3[ 4 ];
			Color[] colors = new Color[ 4 ];
			int[] triangles = new int[ 2 * 3 ];

			vertices[ 0 ] = new Vector3( v1.x, floorHeight, v1.y ) - center;
			vertices[ 1 ] = new Vector3( v2.x, floorHeight, v2.y ) - center;
			vertices[ 2 ] = new Vector3( v1.x, ceilingHeight, v1.y ) - center;
			vertices[ 3 ] = new Vector3( v2.x, ceilingHeight, v2.y ) - center;

			colors[ 0 ] = Color.white;
			colors[ 1 ] = Color.white;
			colors[ 2 ] = Color.white;
			colors[ 3 ] = Color.white;

			triangles[ 0 ] = 0;
			triangles[ 1 ] = 2;
			triangles[ 2 ] = 3;
			triangles[ 3 ] = 0;
			triangles[ 4 ] = 3;
			triangles[ 5 ] = 1;

			mesh.vertices = vertices;
			mesh.colors = colors;
			mesh.triangles = triangles;

			mesh.RecalculateNormals();
			mesh.RecalculateBounds();

			//----------------------------------------------------------------------------------
			// Texture UV
			//----------------------------------------------------------------------------------

			Vector2[] uv = new Vector2[ 4 ];

			float u_offset = ( float ) sd.textureoffset / ( float ) mat.mainTexture.width;
			float v_offset = ( float ) sd.rowoffset / ( float ) mat.mainTexture.height;

			float u_scale = ( float ) WadDefs.PIXELS_PER_UNIT / ( float ) mat.mainTexture.width;
			float v_scale = ( float ) WadDefs.PIXELS_PER_UNIT / ( float ) mat.mainTexture.height;

			float u_width = ( vertices[ 1 ] - vertices[ 0 ] ).magnitude * u_scale;
			float v_height = ( vertices[ 2 ] - vertices[ 0 ] ).magnitude * v_scale;

			Rect uvRect = new Rect();
			uvRect.xMin = 0f;
			uvRect.xMax = u_width;
			if ( pegToBottom )
			{
				uvRect.yMin = 0f;
				uvRect.yMax = v_height;
			}
			else
			{
				uvRect.yMin = 1f - v_height;
				uvRect.yMax = 1f;
			}

			uv[ 0 ] = new Vector2( uvRect.xMin, uvRect.yMin );
			uv[ 1 ] = new Vector2( uvRect.xMax, uvRect.yMin );
			uv[ 2 ] = new Vector2( uvRect.xMin, uvRect.yMax );
			uv[ 3 ] = new Vector2( uvRect.xMax, uvRect.yMax );

			mesh.uv = uv;
			mat.mainTextureOffset = new Vector2( u_offset, -v_offset );

			//-------------------------------------------------------------------------------------
			// Создаем объект и вешаем на него меш c материалом
			//-------------------------------------------------------------------------------------

			GameObject go = CreateGameObject( wall.transform, center, mesh, mat, true, false );
			MapSide side = go.AddComponent<MapSide>();
			
			side.mesh = mesh;
			side.mat = mat;
			side.wall = wall;
			side.pegToBottom = pegToBottom;
			
			if ( isLeftSide )
				wall.lsides[ ( int ) type ] = side;
			else
				wall.rsides[ ( int ) type ] = side;

			if ( upperFlat )
			{
				side.upperFlat = upperFlat;
				side.upperVertexIndex1 = upperFlat.vertexIndices.IndexOf( vIndex1 );
				side.upperVertexIndex2 = upperFlat.vertexIndices.IndexOf( vIndex2 );
				upperFlat.sides.Add( side );
			}

			side.lowerFlat = lowerFlat;
			side.lowerVertexIndex1 = lowerFlat.vertexIndices.IndexOf( vIndex1 );
			side.lowerVertexIndex2 = lowerFlat.vertexIndices.IndexOf( vIndex2 );
			lowerFlat.sides.Add( side );
		}

		public Material CreateMaterial( string texname, bool transparent )
		{
			Shader shader;
			Material mat;
			Texture tex = map.textures[ texname ];

			bool illum = false;
			if ( generateIllumMaterials )
			{
				if ( allAreIllum )
				{
					illum = true;
				}
				else
				{
					foreach ( string illumTexName in illumTexNameParts )
					{
						if ( texname.ToLower().Contains( illumTexName.ToLower() ) )
						{
							illum = true;
							break;
						}
					}
				}
			}

			if ( texname == WadReader.skyTexName )
			{
				shader = Shader.Find( "Wad2Unity/SkyMask" );
				mat = new Material( shader );
			}
			else if ( illum )
			{
				shader = Shader.Find( "Self-Illumin/Diffuse" );
				mat = new Material( shader );
				mat.color = Color.white;
				mat.mainTexture = tex;
				mat.SetTexture( "_Illum", tex );
				mat.SetFloat( "_EmissionLM", illumEmission );
			}
			else
			{
				shader = Shader.Find( transparent ? "Transparent/Diffuse" : "Diffuse" );
				mat = new Material( shader );
				mat.color = Color.white;
				mat.mainTexture = tex;
			}

			mat.name = texname + "_Mat";
			return mat;
		}

		public GameObject CreateGameObject( Transform parent, Vector3 position, Mesh mesh, Material mat, bool isWall, bool isSky )
		{
			//string path = "Assets/Resources/Meshes/" + mesh.name + ".asset";
			//AssetDatabase.CreateAsset( mesh, path );
			//AssetDatabase.Refresh();
			//tex = AssetDatabase.LoadAssetAtPath( "Assets/" + path, typeof( Texture ) ) as Texture;
			//tex.name = texname + "_Tex";
			//map.textures[ texname ] = tex;

			GameObject go = new GameObject( mesh.name );
			go.transform.parent = parent;
			go.transform.localScale = Vector3.one;
			go.transform.localPosition = position;
			
			if ( !isSky || makeSkyMask )
				go.SetMeshToGameObject( mesh, mat );

			if ( !isSky )
				go.isStatic = true;

			MeshObject mo = new MeshObject();
			mo.isWall = isWall;
			mo.transform = go.transform;
			mo.mesh = mesh;
			mo.mat = mat;
			meshObjects.Add( mo );

			if ( makeColliders )
				go.AddComponent<MeshCollider>();
			
			return go;
		}

		public void GenerateLightmapUVs_Unwrapping()
		{
			foreach ( MeshObject mo in meshObjects )
			{
				Unwrapping.GenerateSecondaryUVSet( mo.mesh );
			}
		}

		public void GenerateLightmapUVs()
		{
			Log.WriteLine( "--- Lightmapping UVs ---" );

			// Определяем наименьшую площадь использования UV, чтобы позднее
			// использовать эту площадь для всей геометрии.
			// Это необходмо для того, чтобы lightmap-сетка была однородной.

			float uvArea = 1f;
			foreach ( MeshObject mo in meshObjects )
			{
				mo.uva = Lightmap_CalcUVAndArea( mo );
				float area = mo.uva.z;
				if ( area < MathUtils.EPSILON )
					Log.WriteLine( "Extremely small area, skipped: " + mo.mesh.name );
				else if ( area < uvArea )
					uvArea = area;
			}

			Log.WriteLine( "Minimum UV area: " + uvArea.ToString( "F8" ) );

			// Генерируем UV2

			foreach ( MeshObject mo in meshObjects )
			{
				mo.mesh.uv2 = Lightmap_GenUV( mo, uvArea );
			}
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="mo"></param>
		/// <returns>
		/// x: u-coord
		/// y: v-coord
		/// z: u*v
		/// </returns>
		private Vector3 Lightmap_CalcUVAndArea( MeshObject mo )
		{
			float wx;
			float wy;

			if ( mo.isWall )
			{
				wx = ( ( mo.mesh.vertices[ 1 ] - mo.mesh.vertices[ 0 ] ) ).magnitude;
				wy = ( ( mo.mesh.vertices[ 2 ] - mo.mesh.vertices[ 0 ] ) ).magnitude;
			}
			else
			{
				wx = mo.mesh.bounds.extents.x * 2f;
				wy = mo.mesh.bounds.extents.z * 2f;
			}

			float mul = 1f / Mathf.Max( wx, wy );
			
			float u = wx * mul;
			float v = wy * mul;

			return new Vector3( u, v, u * v );
		}

		private Vector2[] Lightmap_GenUV( MeshObject mo, float uvArea )
		{
			// Calc bounds (uv aabb)
			
			
			float m;
			if ( mo.uva.z < MathUtils.EPSILON )
			{
				m = 1f;
			}
			else
			{
				m = Mathf.Sqrt( uvArea / mo.uva.z );
			}

			// Vertices' UVs

			Vector2[] uv2 = new Vector2[ mo.mesh.vertexCount ];
			if ( mo.isWall )
			{
				float u = mo.uva.x * m;
				float v = mo.uva.y * m;

				uv2[ 0 ] = new Vector2( 0f, 0f );
				uv2[ 1 ] = new Vector2( u, 0f );
				uv2[ 2 ] = new Vector2( 0f, v );
				uv2[ 3 ] = new Vector2( u, v );
			}
			else
			{
				float padding = 0.001f;

				float meshArea = mo.mesh.CalculateArea();
				float boundsArea = mo.mesh.bounds.extents.x * mo.mesh.bounds.extents.z * 4f;
				//m *= meshArea / boundsArea;

				float max_w = Mathf.Max( mo.mesh.bounds.extents.x, mo.mesh.bounds.extents.z ) * 2f;
				float mul = m / ( max_w + padding * 2f );

				for ( int i = 0; i < mo.mesh.vertexCount; i++ )
				{
					uv2[ i ].x = ( mo.mesh.vertices[ i ].x - mo.mesh.bounds.min.x ) * mul;
					uv2[ i ].y = ( mo.mesh.vertices[ i ].z - mo.mesh.bounds.min.z ) * mul;

					uv2[ i ].x = uv2[ i ].x * ( 1f - 2f * padding ) + padding;
					uv2[ i ].y = uv2[ i ].y * ( 1f - 2f * padding ) + padding;
				}
			}

			return uv2;
		}
	}
}
